package com.xiaouyudeguang.common.service.impl;

import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.xiaouyudeguang.common.annotation.DataScope;
import com.xiaouyudeguang.common.constants.Headers;
import com.xiaouyudeguang.common.dao.AbstractDao;
import com.xiaouyudeguang.common.event.LogEvent;
import com.xiaouyudeguang.common.exception.BusinessException;
import com.xiaouyudeguang.common.request.RequestDto;
import com.xiaouyudeguang.common.serializer.EncryptJsonDeserializer;
import com.xiaouyudeguang.common.service.AbstractService;
import com.xiaouyudeguang.common.utils.EncryptUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageImpl;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

@DSTransactional
public class AbstractDSServiceImpl<Dao extends AbstractDao<T>, T> extends ServiceImpl<Dao, T> implements AbstractService<Dao, T> {

    protected Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    protected Dao baseDao;

    @Autowired
    protected HttpServletRequest request;

    @Autowired
    protected ApplicationContext context;

    public <B> B get(Class<B> cls) {
        return context.getBean(cls);
    }

    protected final QueryWrapper<T> wrapper() {
        return new QueryWrapper<>();
    }

    protected final QueryWrapper<T> wrapper(T entity) {
        return new QueryWrapper<>(entity);
    }

    protected final LambdaUpdateWrapper<T> updateWrapper() {
        return new LambdaUpdateWrapper<>();
    }

    protected final LambdaUpdateWrapper<T> updateWrapper(T entity) {
        return new LambdaUpdateWrapper<>(entity);
    }

    protected final LambdaQueryWrapper<T> lambdaWrapper() {
        return new LambdaQueryWrapper<>();
    }

    protected final LambdaQueryWrapper<T> lambdaWrapper(T entity) {
        return new LambdaQueryWrapper<>(entity);
    }

    public final LambdaQueryWrapper<T> lambdaWrapper(String tenantId, String appId) {
        return tenantId.equals(appId) ?
                new QueryWrapper<T>().eq(Headers.APP_ID, appId).lambda() :
                new QueryWrapper<T>().eq(Headers.TENANT_ID, tenantId).lambda();
    }

    public final LambdaQueryWrapper<T> limitWrapper(String tenantId, String appId) {
        return tenantId.equals(appId) ?
                new QueryWrapper<T>().eq(Headers.APP_ID, appId).lambda() :
                new QueryWrapper<T>().and(q -> {
                    q.eq(Headers.APP_ID, appId).eq(Headers.TENANT_ID, appId);
                    q.or().eq(Headers.APP_ID, appId).eq(Headers.TENANT_ID, tenantId);
                }).lambda();
    }

    public LambdaQueryWrapper<T> lambdaWrapper(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        return requestDto.getLambdaWrapper();
    }

    @Override
    public T getById(String tenantId, String appId, String id) throws Exception {
        T data = super.getById(Long.valueOf(id));
        if (data != null) {
            fillData(tenantId, appId, data);
        }
        return data;
    }

    @Override
    public List<T> getByIds(String tenantId, String appId, Set<String> ids) throws Exception {
        List<T> recordList = super.listByIds(ids.stream().map(Long::valueOf).collect(Collectors.toSet()));
        if (isEmpty(recordList)) {
            return emptyList();
        }
        fillData(tenantId, appId, recordList);
        return recordList;
    }

    @Override
    public Boolean deleteById(String tenantId, String appId, String id) throws Exception {
        T data = getById(tenantId, appId, id);
        return data != null && super.removeById(data);
    }

    @Override
    public Boolean deleteByIds(String tenantId, String appId, Set<String> ids) throws Exception {
        List<T> recordList = getByIds(tenantId, appId, ids);
        return isNotEmpty(recordList) && super.removeByIds(recordList);
    }

    @Override
    public Boolean delete(String tenantId, String appId, SFunction<T, ?> function, Object value) throws Exception {
        return super.remove(lambdaWrapper(tenantId, appId).eq(function, value));
    }

    @Override
    public Boolean delete(String tenantId, String appId, SFunction<T, ?> function, Set<?> values) throws Exception {
        return super.remove(lambdaWrapper(tenantId, appId).in(function, values));
    }

    @Override
    @DataScope(DataScope.Scope.UPDATE)
    public T updateById(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        T data = requestDto.getData();
        super.updateById(data);
        return data;
    }

    @Override
    @DataScope(DataScope.Scope.UPDATE)
    public List<T> updateByIds(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        T data = requestDto.getData();
        super.updateById(data);
        return Collections.singletonList(data);
    }

    @Override
    @DataScope(DataScope.Scope.UPDATE)
    public List<T> updateBatch(String tenantId, String appId, List<T> dataList) throws Exception {
        for (T data : dataList) {
            updateById(tenantId, appId, RequestDto.getRequest(data));
        }
        return dataList;
    }

    @Override
    @DataScope(DataScope.Scope.MODIFY)
    public T modify(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        T data = requestDto.getData();
        super.updateById(data);
        return data;
    }

    @Override
    @DataScope(DataScope.Scope.SAVE)
    public T save(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        T data = requestDto.getData();
        save(data);
        return data;
    }

    @Override
    @DataScope(DataScope.Scope.SAVE)
    public List<T> saveBatch(String tenantId, String appId, List<T> dataList) throws Exception {
        for (T data : dataList) {
            save(tenantId, appId, RequestDto.getRequest(data));
        }
        return dataList;
    }

    @Override
    public List<T> upload(String tenantId, String appId, List<T> dataList) throws Exception {
        if (dataList.size() > 100) {
            throw new BusinessException("单次导入不能超过100条");
        }
        for (T data : dataList) {
            save(tenantId, appId, RequestDto.getRequest(data));
        }
        return dataList;
    }

    @Override
    public List<T> export(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        requestDto.setSize(5000);
        return isNotEmpty(requestDto.getIds()) ? getByIds(tenantId, appId, requestDto.getIds()) : page(tenantId, appId, requestDto).getContent();
    }

    @Override
    public T getOne(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        return super.getOne(lambdaWrapper(tenantId, appId, requestDto));
    }

    @Override
    public Boolean exist(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        return isNotEmpty(super.list(lambdaWrapper(tenantId, appId, requestDto)));
    }

    @Override
    public T getOne(String tenantId, String appId, SFunction<T, ?> function, Object value) throws Exception {
        return value == null ? null : super.getOne(lambdaWrapper().eq(function, value));
    }

    @Override
    @DataScope(DataScope.Scope.LIST)
    public List<T> list(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        return super.list(lambdaWrapper(tenantId, appId, requestDto));
    }

    @Override
    public List<T> tree(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        return super.list(lambdaWrapper(tenantId, appId, requestDto));
    }

    @Override
    public List<T> list(String tenantId, String appId, SFunction<T, ?> function, Object value) throws Exception {
        return value == null ? emptyList() : super.list(lambdaWrapper().eq(function, value));
    }

    @Override
    public List<T> like(String tenantId, String appId, SFunction<T, ?> function, Object value) throws Exception {
        return value == null ? emptyList() : super.list(lambdaWrapper().like(function, value));
    }

    @Override
    public List<T> list(String tenantId, String appId, SFunction<T, ?> function, Set<?> values) throws Exception {
        return isEmpty(values) ? emptyList() : super.list(lambdaWrapper().in(function, values));
    }

    @Override
    public List<T> listAll(String tenantId, String appId) throws Exception {
        return super.list();
    }

    @Override
    public List<T> listByMap(String tenantId, String appId, Map<String, Object> params) throws Exception {
        return super.listByMap(params);
    }

    @Override
    @DataScope(DataScope.Scope.PAGE)
    public PageImpl<T> page(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        IPage<T> page = super.page(requestDto.getPage(), lambdaWrapper(tenantId, appId, requestDto));
        List<T> recordList = page.getRecords();
        if (isEmpty(recordList)) {
            return emptyPage();
        }
        fillData(tenantId, appId, recordList);
        return page(recordList, page.getTotal());
    }

    @Override
    @DataScope
    public Page<T> pageAll(String tenantId, String appId, RequestDto<T> requestDto) throws Exception {
        return super.page(requestDto.getPage(), lambdaWrapper(tenantId, appId, requestDto));
    }

    public void publish(String tenantId, String appId, String id, T data) {
        context.publishEvent(new LogEvent(tenantId, appId, id, data));
    }

    /**
     * 批量查询
     *
     * @param lambdaQueryWrapper
     * @param function
     * @param value
     */
    public void fillBatchQuery(LambdaQueryWrapper<T> lambdaQueryWrapper, SFunction<T, ?> function, String value) {
        if (isNotBlank(value)) {
            if (value.contains(",")) {
                lambdaQueryWrapper.in(function, Arrays.asList(value.split(",")));
            } else if (value.contains("，")) {
                lambdaQueryWrapper.in(function, Arrays.asList(value.split("，")));
            } else if (value.contains(";")) {
                lambdaQueryWrapper.in(function, Arrays.asList(value.split(";")));
            } else if (value.contains("；")) {
                lambdaQueryWrapper.in(function, Arrays.asList(value.split("；")));
            } else if (value.contains("&")) {
                lambdaQueryWrapper.in(function, Arrays.asList(value.split("&")));
            } else if (value.contains(" ")) {
                lambdaQueryWrapper.in(function, Arrays.asList(value.split(" ")));
            } else {
                lambdaQueryWrapper.like(function, value);
            }
        }
    }

    /**
     * 范围查询
     *
     * @param lambdaQueryWrapper
     * @param function
     * @param minValue
     * @param maxValue
     */
    protected void fillLimitQuery(LambdaQueryWrapper<T> lambdaQueryWrapper, SFunction<T, ?> function, Object minValue, Object maxValue) {
        //下单时间
        lambdaQueryWrapper.gt(minValue != null, function, minValue);
        lambdaQueryWrapper.lt(maxValue != null, function, maxValue);
    }

    protected void fillData(String tenantId, String appId, T data) {
    }

    protected void fillData(String tenantId, String appId, List<T> dataList) {
    }

    @Override
    public void encrypt(String tenantId, String appId) throws Exception {
        List<T> dataList = list();
        Field[] fields = currentModelClass().getDeclaredFields();
        for (Field field : fields) {
            JsonDeserialize jsonDeserialize =  field.getAnnotation(JsonDeserialize.class);
            if (jsonDeserialize != null && jsonDeserialize.using() == EncryptJsonDeserializer.class) {
                field.setAccessible(true);
                for (T data : dataList) {
                    if (field.get(data) != null) {
                        field.set(data, EncryptUtils.encrypt((String) field.get(data)));
                    }
                }
            }
        }
        updateBatchById(dataList);
    }
}
